Exporting Data from Amazon Aurora MySQL to S3 and Loading It into Amazon Redshift
This guide explains how to export data from Amazon Aurora MySQL into Amazon S3 and then import those CSV files into Amazon Redshift.
The examples use generic testing resources:
- S3 bucket:
test-etl-bucket - IAM role:
test-etl-role - Aurora user:
TestEtlUser - AWS account ID:
123456789012 - Aurora version: MySQL-compatible Aurora 3.x
- AWS Region:
us-east-1
Architecture
The data flow is:
Aurora MySQL
↓ SELECT INTO OUTFILE S3
Amazon S3
↓ Redshift COPY
Amazon Redshift
The IAM role is used by Aurora to write files to S3 and by Redshift to read those files.
Step 1: Create the IAM permissions policy
Create an IAM policy with access to the testing bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::test-etl-bucket"
]
},
{
"Sid": "ReadWriteObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:AbortMultipartUpload"
],
"Resource": [
"arn:aws:s3:::test-etl-bucket/*"
]
}
]
}
Attach this policy to the IAM role:
test-etl-role
For stricter production security, separate roles are preferable:
- Aurora export role with
PutObject - Redshift import role with
GetObject
A shared role is acceptable for initial testing.
Step 2: Configure the IAM trust policy
Use the following trust policy if the role needs to support Aurora, provisioned Redshift, and Redshift Serverless:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"rds.amazonaws.com",
"redshift.amazonaws.com",
"redshift-serverless.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
The role ARN in this example is:
arn:aws:iam::123456789012:role/test-etl-role
If the S3 bucket uses a customer-managed KMS key, also grant the role kms:Decrypt and kms:GenerateDataKey as appropriate, and allow the role in the KMS key policy.
Step 3: Associate the role with Aurora
Creating the IAM role is not enough. It must also be associated with the Aurora cluster.
Open:
Amazon RDS
→ Databases
→ Select the Aurora cluster
→ Connectivity & security
→ Manage IAM roles
→ Add test-etl-role
Wait until the role status becomes Available.
Be sure to select the Aurora cluster, not only its writer instance.
Step 4: Configure the Aurora cluster parameter
For Aurora MySQL version 3, set this DB cluster parameter:
aws_default_s3_role
Set its value to:
arn:aws:iam::123456789012:role/test-etl-role
The parameter must be configured in a custom DB cluster parameter group.
Verify it from MySQL:
SHOW VARIABLES LIKE 'aws_default_s3_role';
Expected result:
arn:aws:iam::123456789012:role/test-etl-role
Reboot the Aurora writer if the parameter change is waiting for a reboot.
Step 5: Grant the Aurora database role
Grant S3 export access to the testing database user:
GRANT AWS_SELECT_S3_ACCESS
TO `TestEtlUser`@`%`;
Make all granted roles active automatically when the user reconnects:
SET DEFAULT ROLE ALL
TO `TestEtlUser`@`%`;
For the current MySQL session, activate the role immediately:
SET ROLE ALL;
Verify it:
SELECT CURRENT_USER(), CURRENT_ROLE();
The result should include:
`AWS_SELECT_S3_ACCESS`@`%`
Application connection pools
MySQL roles are session-specific unless they are configured as default roles. If the application uses a connection pool, SET ROLE ALL and the export must run on the same physical connection.
Example with Node.js:
const connection = await pool.getConnection();
try {
await connection.query("SET ROLE ALL");
const [roles] = await connection.query(`
SELECT CURRENT_USER() AS current_user,
CURRENT_ROLE() AS current_roles
`);
console.log(roles);
await connection.query(`
SELECT *
FROM test_database.test_table
INTO OUTFILE S3
's3://test-etl-bucket/exports/test_table/run_001'
FORMAT CSV HEADER
OVERWRITE ON
`);
} finally {
connection.release();
}
Do not run SET ROLE ALL with one pooled connection and the export with another.
Step 6: Export Aurora data to S3
Run:
SELECT *
FROM test_database.test_table
INTO OUTFILE S3
's3://test-etl-bucket/exports/test_table/run_001'
FORMAT CSV HEADER
OVERWRITE ON;
Aurora treats the S3 destination as an object prefix. It automatically creates files such as:
exports/test_table/run_001.part_00000
exports/test_table/run_001.part_00001
This naming is expected. The files contain CSV data even though their names do not end with .csv.
Aurora does not provide an option to remove the .part_00000 suffix.
If the selected dataset is empty, Aurora can produce a zero-byte object. Avoid including unwanted empty objects when loading data into Redshift.
Step 7: Associate the role with Redshift
Provisioned Redshift
Open:
Amazon Redshift
→ Provisioned clusters
→ Select the cluster
→ Properties
→ Cluster permissions
→ Manage IAM roles
Associate:
arn:aws:iam::123456789012:role/test-etl-role
The equivalent AWS CLI command is:
aws redshift modify-cluster-iam-roles \
--cluster-identifier test-redshift-cluster \
--add-iam-roles \
arn:aws:iam::123456789012:role/test-etl-role
Redshift Serverless
Open:
Amazon Redshift
→ Redshift Serverless
→ Namespace configuration
→ Select the namespace
→ Security and encryption
→ Permissions
Associate test-etl-role with the namespace. It can optionally be configured as the default IAM role.
Step 8: Create the Redshift destination table
The Redshift table must already exist before running COPY.
Its column order and compatible data types must match the Aurora export:
CREATE SCHEMA IF NOT EXISTS staging;
CREATE TABLE staging.test_table (
record_id BIGINT,
record_name VARCHAR(500),
record_status VARCHAR(100),
created_at TIMESTAMP
);
Replace these example columns with the actual columns returned by the Aurora SELECT.
Step 9: Validate the files without loading
Use NOLOAD to validate the CSV format and data types:
COPY staging.test_table
FROM 's3://test-etl-bucket/exports/test_table/run_001'
IAM_ROLE 'arn:aws:iam::123456789012:role/test-etl-role'
FORMAT AS CSV
IGNOREHEADER 1
REGION 'us-east-1'
NOLOAD;
Use IGNOREHEADER 1 only when the Aurora export used:
FORMAT CSV HEADER
If the export did not contain a header, remove IGNOREHEADER 1.
If S3 and Redshift are in the same Region, the REGION option is optional. It is required when the bucket and Redshift are in different Regions.
Step 10: Load the data into Redshift
After NOLOAD succeeds, remove it and run the actual import:
COPY staging.test_table
FROM 's3://test-etl-bucket/exports/test_table/run_001'
IAM_ROLE 'arn:aws:iam::123456789012:role/test-etl-role'
FORMAT AS CSV
IGNOREHEADER 1
REGION 'us-east-1';
If the IAM role is configured as Redshift’s default role, the command can use:
IAM_ROLE default
For example:
COPY staging.test_table
FROM 's3://test-etl-bucket/exports/test_table/run_001'
IAM_ROLE default
FORMAT AS CSV
IGNOREHEADER 1;
Redshift loads every S3 object whose key matches the specified prefix.
Therefore:
s3://test-etl-bucket/exports/test_table/run_001
can load:
run_001.part_00000
run_001.part_00001
run_001.part_00002
Loading multiple files safely
If every object under a folder has the same table structure, the complete folder can be loaded:
COPY staging.test_table
FROM 's3://test-etl-bucket/exports/test_table/'
IAM_ROLE default
FORMAT AS CSV
IGNOREHEADER 1;
Do not load an entire folder when it contains:
- Files for different destination tables
- Different column structures
- Temporary files
- Previous exports
- Unwanted zero-byte files
- Manifest files mixed with data files
In these cases, use a specific prefix or a Redshift manifest.
Example manifest:
{
"entries": [
{
"url": "s3://test-etl-bucket/exports/test_table/run_001.part_00000",
"mandatory": true
},
{
"url": "s3://test-etl-bucket/exports/test_table/run_001.part_00001",
"mandatory": true
}
]
}
Load it with:
COPY staging.test_table
FROM 's3://test-etl-bucket/manifests/test_table_run_001.json'
IAM_ROLE default
FORMAT AS CSV
IGNOREHEADER 1
MANIFEST;
Avoiding duplicate data
Redshift COPY appends rows. It does not automatically replace existing rows or perform an upsert.
For a complete refresh:
BEGIN;
TRUNCATE TABLE staging.test_table;
COPY staging.test_table
FROM 's3://test-etl-bucket/exports/test_table/run_001'
IAM_ROLE default
FORMAT AS CSV
IGNOREHEADER 1;
COMMIT;
For incremental imports, load into a staging table first and then use MERGE based on the business key.
Common errors
Aurora error 1227: SELECT INTO S3 privilege required
Example:
Access denied; you need the SELECT INTO S3 privilege
Cause:
The AWS_SELECT_S3_ACCESS role is granted but not active.
Solution:
SET ROLE ALL;
SELECT CURRENT_ROLE();
For a permanent solution:
SET DEFAULT ROLE ALL TO `TestEtlUser`@`%`;
Aurora error 63985: Missing Credentials
Example:
S3 API returned error:
Missing Credentials: Cannot instantiate S3 Client
Possible causes:
-
aws_default_s3_roleis empty or incorrect - The role is not associated with the Aurora cluster
- The associated role is not in
Availablestatus - The IAM trust policy does not allow
rds.amazonaws.com - The parameter was changed but the writer still requires a reboot
Export works in terminal but fails from application code
Cause:
The terminal session ran SET ROLE ALL, but the application opened a different connection where the role was inactive.
Solution:
- Configure the AWS role as a default database role, or
- Execute
SET ROLE ALLon every new application connection
Redshift S3 AccessDenied
Check the following:
- The role is associated with the correct Redshift cluster or namespace
- The
COPYcommand uses the correct role ARN - The role has
s3:ListBucketands3:GetObject - The bucket policy does not contain an explicit deny
- The KMS key policy allows the role when SSE-KMS is used
- Redshift Serverless trust includes
redshift-serverless.amazonaws.com
Redshift data-format errors
Validate first:
COPY staging.test_table
FROM 's3://test-etl-bucket/exports/test_table/run_001'
IAM_ROLE default
FORMAT AS CSV
IGNOREHEADER 1
NOLOAD;
For provisioned Redshift, inspect recent load errors:
SELECT *
FROM stl_load_errors
ORDER BY starttime DESC
LIMIT 20;
Final checklist
Before running the pipeline, confirm:
- The IAM role has S3 read and write permissions
- The role trusts Aurora and the relevant Redshift service
- The role is associated with the Aurora cluster
-
aws_default_s3_rolecontains the correct role ARN -
AWS_SELECT_S3_ACCESSis active forTestEtlUser - The role is associated with Redshift
- The Redshift destination table already exists
- The column order matches the CSV data
- Header handling is consistent
- The selected S3 prefix contains only the intended files
- Duplicate-load behavior has been considered
Official references:
Top comments (0)