BigQuery’s streaming inserts are fundamentally a transaction log, not a fast data loading mechanism.

Here’s what that means in practice:

Imagine you have a real-time dashboard that needs to show the latest user activity. You’re getting events every second from thousands of users.

// Example streaming insert request payload
{
  "rows": [
    {
      "insertId": "unique_event_id_12345",
      "json": {
        "user_id": 101,
        "event_type": "page_view",
        "timestamp": "2023-10-27T10:00:00Z",
        "page_url": "/products/widget-a"
      }
    },
    {
      "insertId": "unique_event_id_12346",
      "json": {
        "user_id": 102,
        "event_type": "add_to_cart",
        "timestamp": "2023-10-27T10:00:01Z",
        "product_id": "widget-a",
        "quantity": 1
      }
    }
  ]
}

This JSON, sent via the BigQuery API, is processed immediately. BigQuery acknowledges receipt, and within seconds, the data is available for querying. This is the "streaming insert" path. It’s designed for low-latency ingestion, making fresh data accessible almost instantly.

Now, consider a different scenario: you have a daily batch of user logs from a web server, gigabytes in size. You want to load this data into BigQuery for historical analysis.

This is where "batch load" comes in. You’d typically stage these logs in Google Cloud Storage (GCS) as compressed CSV or Avro files.

# Example command to load data from GCS to BigQuery
bq load \
    --source_format=CSV \
    --autodetect \
    my_dataset.user_logs \
    gs://my-log-bucket/logs_2023-10-27.csv.gz

BigQuery then orchestrates a distributed job to read these files, transform them, and write them to its columnar storage. This process takes minutes to hours, depending on the data volume, but it’s far more cost-effective and efficient for large datasets than streaming.

The core problem BigQuery solves with these two distinct paths is the trade-off between data freshness and ingestion cost/efficiency. Streaming inserts offer immediacy but incur higher per-row costs and have API rate limits. Batch loads are cheaper and more scalable for bulk data but introduce latency.

Internally, streaming inserts write data directly into an in-memory buffer that is periodically flushed to persistent storage. This immediate availability comes at the cost of more overhead per record. Batch loads, on the other hand, leverage BigQuery’s highly optimized, distributed data loading infrastructure, which is designed for throughput and cost-efficiency at scale.

Here’s the mental model:

  • Streaming Inserts: Think of it as writing notes on a whiteboard that’s immediately visible. Great for "what’s happening right now." It’s an API call, with associated per-row costs and quotas. The insertId is crucial here for deduplication. If you omit it or use a non-unique one, you might end up with duplicate rows if the same request is retried.
  • Batch Loads: Think of it as filing documents in a very efficient library. You gather them, then send them to be cataloged and stored. Great for "what happened over a period." It uses cloud storage as a staging area and involves a more complex, optimized loading process.

The key lever you control is the method of ingestion: API for streaming, GCS + bq load (or client libraries) for batch. The choice dictates the latency, cost, and scalability of your data pipeline.

One thing most people don’t realize is that streaming inserts aren’t truly immediately queryable in the same way as batch-loaded data. While available within seconds, there’s a brief window where they might not be fully integrated into the query engine’s optimized data structures, potentially leading to slightly slower query performance on very recent streaming data compared to data that has undergone the batch load optimization process. BigQuery’s "streaming buffer" is a temporary holding area.

The next concept to explore is how to combine these two approaches for hybrid real-time and historical analytics using features like MERGE statements.

Want structured learning?

Take the full Bigquery course →