skip to content

What does a Redshift UNLOAD to S3 in Parquet format actually write out?

level: middleimportance: should knowfreq 48%

answer

  1. it is COPY run backwards
  2. every slice writes its own object
  3. one file means one writer
  4. folders named column=value prune scans
  5. a listing file proves the set is complete

basics

~20 s

Each slice writes its own Parquet file directly to S3 under the prefix you give, so one UNLOAD produces many part files in parallel. PARALLEL OFF makes it write serially into a single file instead.

solid answer

~50 s

`UNLOAD ('SELECT ...') TO 's3://bucket/prefix' IAM_ROLE '...' FORMAT AS PARQUET` runs the query on the compute nodes and has every slice write its share of the result straight to S3, producing a set of numbered part files under the prefix. That parallel write is the point — the data never funnels through the leader node. `PARALLEL OFF` serialises it into one file (still split once a maximum file size is reached) and is what you use when the consumer needs a single ordered output. `PARTITION BY (col)` writes Hive-style `col=value/` folders so a downstream engine can prune whole directories, and `INCLUDE` keeps the partition column inside the files as well as in the path. `MANIFEST` emits a JSON list of the files written so a consumer knows the complete set; `ALLOWOVERWRITE` lets the unload replace existing objects.

code

sql · 10 lines
sql
-- Parallel Parquet export, partitioned for downstream pruning
UNLOAD ('SELECT sale_id, sale_ts, channel, net_amount
         FROM sales
         WHERE sale_ts >= ''2026-01-01''')
TO 's3://exports/sales/2026-01-01/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftUnload'
FORMAT AS PARQUET
PARTITION BY (channel)
MANIFEST
ALLOWOVERWRITE;

go deeper

for a junior

Know that UNLOAD exports a query result to S3 and that it produces multiple files by default. Be able to write one with an IAM role and FORMAT AS PARQUET.

for a middle

Explain the parallel write across slices, what PARALLEL OFF and MAXFILESIZE change, and how PARTITION BY lays out Hive-style folders for downstream pruning.

for a senior

Design the export contract: per-run prefixes, manifests so consumers see a complete set, partition columns chosen for the reader's filters, and Parquet output that an external table can sit on directly.

for a principal

Decide what leaves the warehouse at all, in what format and under whose ownership — cold-data offload strategy, cost of lake storage versus cluster storage, and the interface other teams build against.

## What UNLOAD is for `UNLOAD` is the mirror image of `COPY`: it takes a query result and writes it to S3 in parallel. It is how a Redshift cluster hands data to the rest of the platform — a data lake, another engine, an archive, a partner feed — without dragging the result set through a client connection. ```sql UNLOAD ('SELECT sale_id, sale_ts, channel, net_amount FROM sales WHERE sale_ts >= ''2026-01-01''') TO 's3://exports/sales/2026-01-01/' IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftUnload' FORMAT AS PARQUET PARTITION BY (channel); ``` Note the doubled quotes: the query is a string literal, so single quotes inside it must be escaped. ## Parallel by default Every slice writes its own portion of the result directly to S3, producing a set of numbered part files under the prefix. This is why an `UNLOAD` of a large table finishes in a fraction of the time a client-side export would take, and why you get many files rather than one. The consumer is expected to treat the prefix as the unit, not any individual file. `PARALLEL OFF` turns that off: the result is written serially to a single object, which is split only when the maximum file size is exceeded. Two reasons to use it: the downstream consumer genuinely needs one file, or the result must preserve the query's `ORDER BY` across the whole output — with parallel writes, ordering only holds within a file. The cost is that the write becomes a single-threaded operation, so reserve it for small results. `MAXFILESIZE` sets the size at which a new file is started, letting you tune output granularity for whatever reads the data next. ## Why Parquet rather than text `FORMAT AS PARQUET` writes a columnar, self-describing, compressed file. Compared to delimited text it is smaller on S3, carries column types so the consumer does not have to infer or configure them, and lets a downstream query engine read only the columns it needs. It is also the format a lake table or an external table over the same S3 location will expect. Format-specific text options — delimiters, headers, quoting, escaping — do not apply to a Parquet unload; the format carries that information itself. ## Partitioning the output `PARTITION BY (col1, col2)` writes Hive-style directories: `channel=web/`, `channel=store/`, and so on. Anything reading the prefix afterwards can eliminate whole directories from a scan by looking at the path alone, which is the same pruning idea a partitioned table uses internally. By default the partition columns are represented only in the path; adding `INCLUDE` keeps them as columns inside the files too, which matters when the consumer reads a single file out of context and still needs the value. Partitioning on a high-cardinality column is the classic mistake here — it produces an enormous number of small directories and files, and the per-object overhead on the read side outweighs any pruning benefit. ## Knowing the write is complete `MANIFEST` writes an additional JSON file listing the objects the unload produced. A consumer that reads the manifest sees exactly the file set of this run, which protects it from picking up leftovers from a previous export or reading a prefix mid-write. Combined with `ALLOWOVERWRITE` — which permits the unload to replace objects already at the destination — and a per-run prefix, this is how repeatable exports are built. Without `ALLOWOVERWRITE`, an unload to a prefix that already contains matching files fails rather than silently mixing two runs' output. That default is deliberately conservative and worth keeping: write each run to its own dated prefix instead of overwriting in place, and the ambiguity disappears. ## Typical uses - **Offloading cold history.** Unload old partitions of a fact table to Parquet in S3, then delete them from the cluster, keeping them queryable through an external table over the same location. - **Feeding other engines.** Any tool that reads Parquet from object storage can consume the output directly. - **Cheap, portable backups.** Parquet on S3 is far cheaper than warehouse storage, self-describing, and restorable with a `COPY`. ## What interviewers listen for That the write is parallel across slices and produces many files; that `PARALLEL OFF` exists and why you would accept its cost; that partitioned output is about pruning on the read side; and that Parquet is chosen for typing and columnar reads, not just for size.

  • When is PARALLEL OFF worth its cost?
    When the consumer cannot handle a file set, or when the global ordering from an ORDER BY must survive into the output — with parallel writes, ordering holds only within each file. The price is a single-threaded write, so it suits small aggregated results and is a poor choice for exporting a large fact table.
  • What does PARTITION BY (event_date) INCLUDE change compared with PARTITION BY (event_date)?
    Both write Hive-style event_date=<value> folders so a reader can skip whole directories. Without INCLUDE the column exists only in the path and is dropped from the file contents; with INCLUDE it is written into the files as well. Use INCLUDE when a consumer may read a file directly, without interpreting the directory structure.
  • Why unload to Parquet rather than gzipped CSV?
    Parquet is columnar, typed and self-describing, so a consumer reads only the columns it needs and never has to infer or configure types, delimiters or quoting. It is also what an external table over the same S3 location expects. Gzipped CSV remains reasonable for interchange with tools that cannot read Parquet.

saying these in an interview costs you the question

  • Expecting a single output file from a default UNLOAD
  • Thinking ORDER BY holds across parallel output files
  • Partitioning the output on a high-cardinality column
  • Believing the result streams through the leader node
  • Assuming UNLOAD overwrites existing files by default

context