blob: 18ca7503c9b69419816b2b45eb37bc5a058174d0 [file]
---
title: Batch Load
description: Asynchronous bulk ingestion from S3, HDFS, and external catalogs into Doris via LOAD LABEL or INSERT INTO SELECT, with label-based dedup.
keywords:
- batch load
- LOAD LABEL
- INSERT INTO SELECT
- S3 ingestion
- HDFS ingestion
- bulk ingestion
- Apache Doris ETL
slug: /key-features/batch-load
image: /images/next/key-features/batch-load.jpg
last_update:
date: 2026-05-10
author: Apache Doris
featureCard:
tags:
- data-loading
- bulk-ingestion
---
> **TL;DR** Apache Doris Batch Load moves large files from S3, HDFS, or another warehouse into Doris in one shot. You submit a `LOAD LABEL` statement, the FE plans the work and fans it out to BEs, and you check progress with `SHOW LOAD`. The job is asynchronous, so the client can disconnect; the label is what lets you find the result and what dedupes a retry. For [external catalogs](./multi-catalog) and TVF reads, the synchronous `INSERT INTO ... SELECT` form covers the same ground with simpler ETL.
![Apache Doris Batch Load: Asynchronous bulk ingestion from S3, HDFS, and external catalogs into Doris via LOAD LABEL or INSERT INTO SELECT, with label-based dedup.](/images/next/key-features/batch-load.jpg)
## Why use batch load in Apache Doris? {#why}
Apache Doris Batch Load is the right tool when a single ingestion job runs for hours and the warehouse, not the client, has to own its lifecycle. Real-time loaders are the right tool for events trickling in, but they fall apart when you need to move a TB of historical data, or rebuild a fact table from a Hive snapshot, or backfill last quarter from Parquet on S3. A [Stream Load](./stream-load) over HTTP times out. A per-row `INSERT` would take days. The job has to run server-side, retry on its own, and survive the client losing its connection.
- A nightly ETL has to land a few hundred GB of Parquet from S3 before the morning dashboards refresh.
- A migration from Snowflake or Hive needs to copy whole partitions, with column-level filters and type casts.
- A backfill of one missing day cannot block the connection that submitted it.
Batch Load handles all three by treating large bulk ingestion as a long-running, label-tracked job that the warehouse owns end to end.
## What is Apache Doris batch load? {#what}
Apache Doris Batch Load is the family of asynchronous bulk-load methods in Doris. The main entry point is the `LOAD LABEL ... WITH S3|HDFS|BROKER` statement, historically called Broker Load, which today covers S3, HDFS, and any storage system reachable through a Broker process. The synchronous `INSERT INTO ... SELECT` variant covers the same ingestion needs when the source is an external catalog (Hive, Iceberg, JDBC) or a file behind a TVF such as `S3()` or `HDFS()`. Both share the same transaction model as the rest of Doris loading: one label per job, atomic commit, replica-level publish.
**Key terms**
- **`LOAD LABEL`**: the SQL entry point for an asynchronous Broker Load. The label is per-database and dedupes retries.
- **`WITH S3` / `WITH HDFS` / `WITH BROKER`**: the access clause that picks the storage backend. `S3` and `HDFS` are built into the BE; `BROKER broker_name` routes through an external Broker process for everything else.
- **`SHOW LOAD`**: the FE-side view that reports state, progress, error URL, and the parsed `EtlInfo`.
- **TVF (Table-Valued Function)**: `S3(...)`, `HDFS(...)`, `LOCAL(...)`, `iceberg_meta(...)`, and the like. They expose files or external metadata as a table you can `SELECT` from, then pipe into `INSERT INTO ... SELECT` for synchronous ingestion.
- **`max_filter_ratio`**: the per-job tolerance for malformed rows. Default is zero, so a single bad row cancels the load.
## How does Apache Doris batch load work? {#how}
Apache Doris Batch Load registers a labeled job on the FE, parallelizes file scans across BEs, and atomically commits the result once every replica publishes the new version.
1. **Submit and label.** A `LOAD LABEL my_db.daily_orders ...` request lands on the FE. The FE registers the label, opens a transaction, and the load enters `PENDING`. Resubmitting the same label inside the retention window short-circuits to the original job.
2. **Plan and fan out.** The FE estimates file sizes (one BE handles between `min_bytes_per_broker_scanner` and `max_bytes_per_broker_scanner`, default 64 MB to 500 GB), splits the work, and dispatches scanner tasks to BEs. Job state moves through `LOADING`.
3. **Read and write in parallel.** Each BE pulls its slice of the source data (S3, HDFS, or through a Broker), parses CSV/JSON/Parquet/ORC, applies any `SET` expressions, `WHERE` filter, or `COLUMNS FROM PATH` extraction, and writes segments into the target tablets. Malformed rows count against `max_filter_ratio`; the bad-row sample is exposed via the URL field.
4. **Commit and publish.** When every BE acknowledges, the FE commits the transaction. A `PublishVersion` task ships to every replica. When all replicas confirm, the job becomes `FINISHED` and the rows are queryable. A failure anywhere along the way moves the job to `CANCELLED` and discards the data.
5. **Inspect with SHOW LOAD.** `SHOW LOAD WHERE LABEL = ...` reports `State`, `Progress`, `EtlInfo` (rows scanned, rows filtered, rows in error), and the error URL when something went wrong.
The `INSERT INTO target SELECT * FROM s3(...)` form takes the same plan shape but runs synchronously. The connection has to stay open until the load finishes; in exchange you get an immediate result and SQL-level composition (joins, type casts, predicate pushdown into the source).
## Quick start {#quick-start}
```sql
LOAD LABEL testdb.orders_2024_q1
(
DATA INFILE("s3://my-bucket/orders/2024-q1/*.parquet")
INTO TABLE orders
FORMAT AS "parquet"
(order_id, customer_id, amount, order_date)
)
WITH S3 (
"provider" = "S3",
"AWS_ENDPOINT" = "https://s3.us-west-2.amazonaws.com",
"AWS_REGION" = "us-west-2",
"AWS_ACCESS_KEY" = "<ak>",
"AWS_SECRET_KEY" = "<sk>"
)
PROPERTIES ("timeout" = "3600", "max_filter_ratio" = "0.01");
```
**Expected result**
```
+----------+--------------------+----------+------------------+--------+
| JobId | Label | State | Progress | Type |
+----------+--------------------+----------+------------------+--------+
| 41326624 | orders_2024_q1 | FINISHED | ETL:100%;LOAD:100% | BROKER |
+----------+--------------------+----------+------------------+--------+
```
`SHOW LOAD ORDER BY CreateTime DESC LIMIT 1` returns when the job is `FINISHED`. Up to 1% of malformed rows are tolerated; anything more cancels the job and the URL field points at a sample of the offending rows.
## When should you use Apache Doris batch load? {#when}
Use Apache Doris Batch Load when a single ingestion run handles tens of GB or more from object storage, an external warehouse, or a Hive-partitioned directory.
**Good fit**
- Periodic ingestion of large CSV, JSON, Parquet, or ORC files from S3, HDFS, OSS, COS, OBS, or any S3-compatible storage.
- One-off migrations from Hive, Iceberg, Snowflake, or BigQuery: combine [Catalog](../lakehouse/lakehouse-overview) with `INSERT INTO ... SELECT` for source-side projection and pushdown.
- Backfills measured in tens of GB to hundreds of GB per job, where the client cannot stay connected for hours.
- Loads that need server-side `WHERE` filtering, column transformations via `SET`, or partition fields extracted from the file path with `COLUMNS FROM PATH AS`.
- Hive-partitioned directories matched with wildcards (`*`, `?`, `{1..10}`) inside one statement.
**Not a good fit**
- Continuous Kafka ingestion. Use [Routine Load](./kafka-cdc-integration) so the FE owns the consumer and offsets.
- Per-event JDBC writes from a microservice. The commit overhead per job is wasted on tiny payloads. Reach for [Group Commit](./group-commit) instead.
- Loads from local files on the client machine. Broker Load reads from remote storage only; `Stream Load` or [Doris Streamloader](../connection-integration/data-integration/doris-streamloader) is the right tool.
- Single jobs over a few TB. The default 4-hour timeout becomes a footgun, retries are expensive, and a partial failure replays the whole batch. Split the input by date or directory and submit several jobs.
- Continuous incremental file pickup from S3. A nightly Broker Load works; per-minute polling does not. Use [Streaming Job continuous load](../data-operate/import/import-way/streaming-job/continuous-load-s3) so the FE tracks which files have been ingested.
## Further reading {#further-reading}
- [Stream Load](./stream-load): the synchronous HTTP load path for smaller, real-time batches.
- [Group Commit](./group-commit): server-side merging for the high-frequency small-batch end of the spectrum.
- [Broker Load reference](../data-operate/import/import-way/broker-load-manual): full SQL surface, Kerberos and HDFS HA configuration, scenario-by-scenario examples.
- [INSERT INTO Select](../data-operate/import/import-way/insert-into-manual): the synchronous variant, including TVF and Catalog flows.
- [Load overview decision table](../data-operate/import/load-manual): which method fits which scenario across real-time, streaming, and batch.
- [Catalog and TVF entry point](../lakehouse/file-analysis): how `S3()`, `HDFS()`, and friends expose files as queryable tables.
- [Load Transactions](./load-transaction): the label, lifecycle states, and atomic commit model that batch loads share with the rest of Doris.
- [BROKER LOAD SQL reference](../sql-manual/sql-statements/data-modification/load-and-export/BROKER-LOAD): the canonical statement grammar and every option.