How to Load Data into Amazon Redshift
Most data gets into Amazon Redshift one way: you move files to S3, attach an IAM role to your cluster, and run a COPY command. That is the default recommended approach for bulk loads. This guide covers it in full, including CSV, JSON, and Parquet formats, manifest files, AWS Glue, Amazon Data Firehose, and how to diagnose load errors when things go wrong.
The short answer for beginners
If you are new to Redshift and just need to get data in, here is the default path:
- Upload your data files to an S3 bucket
- Create an IAM role that allows Redshift to read from that bucket
- Attach the role to your Redshift cluster or namespace
- Run a
COPYcommand that points at the S3 path
That is it. Everything else on this page — Parquet vs CSV, manifest files, Glue, Firehose — builds on that foundation. Start there if you are not sure where to begin.
Imagine you need to stock a warehouse with 10,000 boxes. You could carry them in one by one, or you could back a forklift up to the loading dock and move them all at once. INSERT is the person with one box. COPY is the forklift. For large datasets, the difference is hours versus minutes.
Do not load large datasets with INSERT statements in a loop. Every INSERT creates a separate transaction. Loading 1 million rows this way can take hours. The same data loaded with a single COPY takes minutes. Always stage in S3 first, then use COPY.
How Redshift loading works
Almost every Redshift load follows the same path, regardless of whether the source is a database, an application, or a flat file export:
- Source system: data originates from a database (RDS, DynamoDB), an application log, or an export from another service
- S3 staging: the data lands in Amazon S3 as one or more files (CSV, JSON, Parquet, ORC, or Avro)
- IAM role: your Redshift cluster has an IAM role attached that grants read access to the S3 bucket
- COPY command: you run COPY in Redshift, which reads the S3 files in parallel across all compute nodes
- Validate and troubleshoot: if the load succeeds, confirm the row count; if it fails, check STL_LOAD_ERRORS
S3 sits in the middle because Redshift’s COPY command is built to read from it at high throughput. The cluster’s MPP architecture means each node reads a different subset of your files simultaneously, which is why COPY scales with both data volume and cluster size.
When to load data into Redshift
Good use cases
- Bulk loads from S3: one-time or recurring exports from another system, uploaded to S3 and loaded with COPY
- Batch pipelines: nightly or hourly ETL jobs that move data from operational databases into the warehouse for reporting
- Streaming ingestion: events delivered via Amazon Data Firehose, which stages in S3 and issues COPY automatically
- Transformed loads: data read from RDS, reshaped with AWS Glue or another ETL tool, and written to Redshift
When Redshift is the wrong tool
Redshift is not an OLTP database. If your application needs to write individual rows in real time (recording user events, updating order status, serving API responses), use RDS or DynamoDB for that. Redshift is designed for analytical queries on pre-collected data, not live application writes.
If you are deciding whether Redshift or a data lake is the right landing zone, the data warehouses vs data lakes guide covers the decision in detail.
Setting up the IAM role for COPY
Before you can use COPY, your Redshift cluster needs an IAM role attached to it. The cluster uses this role to authenticate to S3. You cannot pass your own access keys to COPY.
You cannot hand your personal keycard to a delivery driver and expect them to access the building. The driver needs their own badge issued to them specifically. Redshift works the same way: the cluster needs its own IAM role (its badge) issued to the Redshift service, not borrowed from your personal credentials.
Step 1: Create the IAM role
aws iam create-role \
--role-name RedshiftS3LoadRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "redshift.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name RedshiftS3LoadRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccessIn production, replace the managed policy with an inline policy that restricts access to your specific bucket rather than all of S3.
Step 2: Attach the role to the cluster
# Provisioned cluster
aws redshift modify-cluster-iam-roles \
--cluster-identifier my-cluster \
--add-iam-roles arn:aws:iam::123456789012:role/RedshiftS3LoadRole
# Redshift Serverless: attach to the namespace instead
aws redshift-serverless update-namespace \
--namespace-name my-namespace \
--iam-roles arn:aws:iam::123456789012:role/RedshiftS3LoadRoleFor Redshift Serverless, the role is associated with the namespace, not a cluster.
The command above uses update-namespace rather than modify-cluster-iam-roles.
COPY from CSV
CSV is the most common format for simple loads. Point COPY at an S3 prefix and it loads all files under that path:
COPY orders (order_id, customer_id, order_date, region, product_id, quantity, unit_price, total_amount)
FROM 's3://my-data-bucket/orders/2025/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS CSV
IGNOREHEADER 1
DATEFORMAT 'YYYY-MM-DD'
TIMEFORMAT 'auto'
MAXERROR 10
REGION 'us-east-1';Key options:
IGNOREHEADER 1skips the header row in each fileMAXERROR 10allows up to 10 rows to fail before COPY aborts. Useful during initial data exploration, but remove it once your data is clean.REGIONis required when the S3 bucket and the cluster are in different AWS regions
COPY from compressed CSV
Redshift automatically decompresses gzip, bzip2, lzo, and zstd files. Just declare the compression type:
COPY orders
FROM 's3://my-data-bucket/orders/2025/data.csv.gz'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS CSV
IGNOREHEADER 1
GZIP;Compressed files transfer faster from S3 and cost less to store. For recurring loads, compressing CSV files with gzip or zstd is worth the extra step at the source.
COPY from Parquet
Parquet is a columnar file format. It stores data in a compressed, typed format that Redshift can read more efficiently than CSV because column types are already encoded in the file.
COPY orders
FROM 's3://my-data-bucket/orders/parquet/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS PARQUET;No IGNOREHEADER, DATEFORMAT, or delimiter options are needed.
Parquet files embed schema information, so Redshift does significantly less parsing work.
Parquet also supports predicate pushdown in Redshift Spectrum queries, letting you query
data directly in S3 without loading it into a table.
For any recurring pipeline where you control the source format, generate Parquet. It loads faster, stores smaller, and requires fewer COPY options than CSV. Tools like AWS Glue, Apache Spark, and Python’s pandas library can all write Parquet natively.
COPY from JSON
JSON loads use either auto-detection or a jsonpaths file that maps JSON keys to table columns. Auto works when your JSON keys exactly match your column names:
COPY orders
FROM 's3://my-data-bucket/orders/json/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS JSON 'auto';If JSON keys do not match column names, create a jsonpaths file in S3 and reference it:
{
"jsonpaths": [
"$.orderId",
"$.customerId",
"$.orderDate",
"$.totalAmt"
]
}COPY orders (order_id, customer_id, order_date, total_amount)
FROM 's3://my-data-bucket/orders/json/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS JSON 's3://my-data-bucket/config/orders_jsonpaths.json';Using manifest files
A manifest file is a JSON document that explicitly lists which S3 files to load. Use it when you want to load a specific set of files rather than everything under a prefix. This is useful for loading only the files produced by a specific pipeline run.
{
"entries": [
{"url": "s3://my-data-bucket/orders/2025/q1/part-001.csv", "mandatory": true},
{"url": "s3://my-data-bucket/orders/2025/q1/part-002.csv", "mandatory": true},
{"url": "s3://my-data-bucket/orders/2025/q1/part-003.csv", "mandatory": true}
]
}COPY orders
FROM 's3://my-data-bucket/manifests/orders_q1_2025.manifest'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS CSV
IGNOREHEADER 1
MANIFEST;Setting “mandatory”: true causes COPY to fail if any listed file is missing.
Without this, a missing file is silently skipped, which is a common source of subtle data
quality bugs in production pipelines.
Loading with AWS Glue
When your data needs transformation before it reaches Redshift (reading from RDS, joining tables, renaming columns, filtering records), AWS Glue is the right tool. Glue is a managed Spark environment for running ETL jobs without managing infrastructure.
A Glue job reads from the source, applies transformations, writes the result to an S3 staging location, and issues the COPY command to Redshift automatically. You do not write the COPY command yourself. If you are deciding whether to transform before or after loading, the ETL vs ELT guide covers the trade-offs.
Loading with Amazon Data Firehose
Amazon Data Firehose (formerly Kinesis Data Firehose) is the lowest-effort way to get streaming data into Redshift. You configure a delivery stream with Redshift as the destination, and Amazon Data Firehose handles everything in between.
Under the hood, Amazon Data Firehose buffers incoming records in S3, then automatically issues a COPY command at a configurable interval (60 seconds to 15 minutes). You configure:
- The Redshift cluster endpoint and database
- The target table and column mapping
- The S3 staging bucket where records are buffered before loading
- The COPY options to use
- The IAM role for Firehose to access both S3 and Redshift
The trade-off is latency. Data is not available in Redshift the moment it arrives. If you need sub-minute latency or more control over processing logic, look at Kinesis Data Streams with a Lambda consumer instead.
How it works in practice: the common load path
Here is a complete end-to-end walkthrough of the standard Redshift load. See Running Your First Redshift Query for a hands-on version that also covers creating the table.
1. Data lands in S3
Upload your data files to an S3 bucket under a consistent prefix, for example
s3://my-bucket/orders/2025/05/. For large datasets, split into multiple files
so Redshift can load them in parallel.
2. Confirm the IAM role is attached
In the AWS console, open the Redshift cluster or serverless namespace and check the IAM roles
tab. The role must have s3:GetObject and s3:ListBucket on the
target bucket.
3. Run COPY
COPY orders
FROM 's3://my-data-bucket/orders/2025/05/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftS3LoadRole'
FORMAT AS PARQUET;4. Verify the result
SELECT COUNT(*) FROM orders;
SELECT order_date, COUNT(*) AS row_count
FROM orders
GROUP BY order_date
ORDER BY order_date DESC
LIMIT 5;5. Inspect load errors if needed
SELECT filename, line_number, colname, raw_field_value, err_reason
FROM STL_LOAD_ERRORS
ORDER BY starttime DESC
LIMIT 20;COPY vs INSERT
For any significant data volume, COPY is the right choice. INSERT is fine for adding a handful of rows manually, but it does not scale.
| Characteristic | COPY from S3 | INSERT |
|---|---|---|
| Performance at scale | Parallelised across all nodes and slices | Sequential, one row per transaction |
| Suitable volume | Thousands to billions of rows | Up to a few hundred rows |
| Compression support | Yes (gzip, bzip2, zstd, lzo) | Not applicable |
| Format support | CSV, JSON, Parquet, ORC, Avro | SQL values only |
| Error visibility | STL_LOAD_ERRORS system table | Inline SQL error message |
Choosing a file format: CSV, Parquet, and JSON
| Format | Best for | Drawbacks | COPY complexity |
|---|---|---|---|
| Parquet | Large or recurring production pipelines | Requires tools to generate (Glue, Spark, pandas) | Minimal: schema is embedded in the file |
| CSV | One-off loads, simple exports | All values are text; types must be declared or inferred | Low: IGNOREHEADER and DATEFORMAT often needed |
| JSON | Event data from APIs or Firehose | Verbose; slower to parse than Parquet | Medium: jsonpaths file needed if keys differ from column names |
AWS Glue vs Amazon Data Firehose
| Characteristic | AWS Glue | Amazon Data Firehose |
|---|---|---|
| Primary use case | Batch ETL with transformations | Continuous streaming delivery |
| Transformation support | Full Spark transformations | Limited (basic schema mapping) |
| Latency | Minutes (batch) | 60 seconds to 15 minutes (buffer) |
| Setup complexity | Higher: Glue job, IAM, and triggers | Lower: delivery stream configuration |
| COPY issued by | Glue DynamicFrame writer | Firehose automatically |
The choice comes down to whether you need transformation logic. If you are just moving data from a source to Redshift with no reshaping, Firehose is simpler. If you need to join, filter, or reformat data before it lands in the warehouse, Glue gives you the flexibility to do that. The Designing Data Pipelines guide covers end-to-end pipeline architecture with S3, Glue, and Redshift.
Performance and reliability best practices
Split large files for parallel loading
Redshift loads files in parallel: each slice in the cluster reads a different file simultaneously.
If you load a single 10 GB file, one slice does all the work, just like every shopper queuing for one checkout. Split that same data into 100 files and all slices work at once, like opening all the lanes at the same time. A good target is file sizes between 1 MB and 1 GB, with the number of files being a multiple of your cluster’s slice count.
Avoid loading many tiny files
The parallel loading benefit disappears when files are too small. Hundreds of kilobyte-sized files create overhead without meaningful parallelism. Combine tiny files in S3 before loading, or use a manifest file to load them all in a single COPY command.
Use compression
Compressed files transfer faster from S3 and cost less to store. Gzip and zstd are good choices for CSV files. Parquet applies column-level compression automatically.
Prefer Parquet for recurring pipelines
Parquet reduces COPY parsing overhead, supports predicate pushdown for Spectrum queries, and is generally more compact than compressed CSV for columnar data. For ad-hoc or one-off loads where you cannot control the source format, CSV is fine.
Run ANALYZE after large loads
Redshift’s query planner uses statistics about each table to build efficient query plans. After loading a large batch of new data, run:
ANALYZE tablename;Stale statistics cause the planner to choose poor execution strategies, such as a full table scan when a more selective plan would be much faster. See Redshift Performance Optimisation for a full guide to VACUUM, ANALYZE, and workload management.
Troubleshooting load failures
When a COPY command fails or only partially loads, Redshift stores the details in system
tables. Start with STL_LOAD_ERRORS:
SELECT
filename,
line_number,
colname,
type,
col_length,
raw_field_value,
err_reason
FROM STL_LOAD_ERRORS
ORDER BY starttime DESC
LIMIT 20;For character-level detail on encoding issues, also check STL_LOADERROR_DETAIL:
SELECT
d.bytenum,
d.raw_field_value,
e.err_reason
FROM STL_LOADERROR_DETAIL d
JOIN STL_LOAD_ERRORS e ON d.query = e.query
ORDER BY d.bytenum;Common COPY errors and what they usually mean
“Invalid digit, Value ‘N’, Pos 0, Type: Integer” — a column Redshift expects to be numeric contains a text value, such as the string
NULLor an empty field. Check for stray text or NULLs in numeric columns.“Extra column(s) found” — the file has more columns than the table definition. Either list the expected columns explicitly in the COPY statement, or update the table schema to match the file.
“Date out of range” — the date format in the file does not match what COPY expects. Try
DATEFORMAT ‘auto’to let Redshift infer the format.“Delimiter not found” — the file uses a different delimiter than COPY assumed (the default is a pipe
|, not a comma). SetDELIMITER ’,’explicitly for comma-separated files.“S3ServiceException: Access Denied” — the IAM role attached to the cluster does not have read access to the S3 bucket or the specific prefix. Check the role’s policy and confirm the bucket name and path are correct.
If you used MAXERROR during a load, bad rows were silently skipped rather
than failing the command. Always query STL_LOAD_ERRORS after any load that used MAXERROR
to confirm which rows were dropped and whether that is acceptable. Remove MAXERROR once
your data is clean.
To see the full history of COPY commands including successful ones, query
STL_LOAD_COMMITS. The STL_FILE_SCAN table shows which files
were read and how many rows were loaded from each.
Common beginner mistakes
Using INSERT for bulk loads. A loop of INSERT statements loading 1 million rows takes many times longer than a single COPY. Stage your data in S3 first and load with COPY, even for one-off loads.
Forgetting to attach the IAM role. This is the most common COPY error for new Redshift users. Confirm the role is attached to the cluster (or namespace for Serverless) and has
s3:GetObjectands3:ListBucketon the target bucket. Check the IAM roles tab before running COPY.Loading a single large file instead of splitting it. One file means one slice does all the work. Split large datasets into multiple files (1 MB to 1 GB each) so all slices load in parallel. On larger clusters this can cut load time by an order of magnitude.
Leaving MAXERROR on in production. MAXERROR is useful during initial data exploration, but leaving it on in production means bad rows are silently skipped. Remove it once the data is clean, and always check STL_LOAD_ERRORS after any load that used MAXERROR.
Not running ANALYZE after a large load. The query planner relies on column statistics to build efficient plans. After loading large amounts of new data, run
ANALYZE tablename;to update those statistics.
Summary
- For bulk loads, always use COPY from S3: it parallelises across all nodes and is orders of magnitude faster than INSERT
- Redshift uses an IAM role attached to the cluster (not your personal credentials) to read from S3
- Parquet is the preferred format for large or recurring pipelines; CSV works for simple or legacy loads
- Manifest files let you specify exactly which S3 files to load and prevent silent partial loads
- Use AWS Glue when data needs transformation before loading; use Amazon Data Firehose for continuous streaming delivery
- When a load fails, check STL_LOAD_ERRORS first, then STL_LOADERROR_DETAIL for character-level encoding detail
- Split files for parallel loading, compress where possible, and run ANALYZE after large loads
Frequently asked questions
What is the best way to load data into Redshift?
Use the COPY command to load files from S3. COPY runs in parallel across all compute nodes and slices, making it dramatically faster than INSERT for any significant data volume. For large production pipelines, store files in S3 as Parquet and run COPY from there.
Should I use CSV, JSON, or Parquet to load into Redshift?
Parquet is the best choice for large or recurring loads. It embeds schema information, compresses well, and loads faster because Redshift does not need to parse column types. CSV is fine for simple one-off loads or when you cannot control the source format. JSON works but is slower to parse and generates larger files than Parquet.
Why does Redshift need an IAM role for COPY?
The COPY command runs on the Redshift cluster, not on your local machine. When Redshift reads files from S3, it authenticates using the IAM role attached to the cluster rather than your personal credentials. The role needs s3:GetObject and s3:ListBucket on the target bucket. You attach the role to the cluster once and reference its ARN in every COPY command.
How do I troubleshoot a failed COPY command?
Query STL_LOAD_ERRORS: SELECT filename, line_number, colname, raw_field_value, err_reason FROM STL_LOAD_ERRORS ORDER BY starttime DESC LIMIT 20. This shows exactly which file, which row, and what the error was. For character-level detail on encoding issues, also check STL_LOADERROR_DETAIL.
When should I use AWS Glue or Amazon Data Firehose instead of COPY directly?
Use AWS Glue when source data needs transformation before loading: reading from RDS, joining tables, renaming columns, or filtering records. Glue writes the transformed output to S3 and issues COPY internally. Use Amazon Data Firehose when you have a stream of events that should be delivered to Redshift continuously. Firehose buffers records in S3 and issues COPY automatically on a configurable interval.