---
title: "CSV vs Parquet Benchmark: 6.8x Smaller, 22-60x Faster"
description: "Parquet/ZSTD is 6.8x smaller than CSV and 22-60x faster on 11.2M NYC taxi rows in DuckDB. Full method, result tables, and reproduction commands."
canonical: "https://motherduck.com/learn/csv-vs-parquet-benchmark/"
related:
  - title: "Parquet | MotherDuck Docs"
    url: "https://motherduck.com/docs/integrations/file-formats/parquet/"
  - title: "Python Faker for DuckDB Fake Data Generation"
    url: "https://motherduck.com/blog/python-faker-duckdb-exploration/"
  - title: "Claudeception: Inside the Mind of an Analytics Agent"
    url: "https://motherduck.com/blog/claudeception-inside-the-mind-of-an-analytics-agent/"
---

# CSV vs Parquet Benchmark: 6.8x Smaller, 22-60x Faster

> Parquet/ZSTD is 6.8x smaller than CSV and 22-60x faster on 11.2M NYC taxi rows in DuckDB. Full method, result tables, and reproduction commands.


This benchmark measured CSV against Parquet on 11.2 million real NYC yellow taxi trips in DuckDB. Parquet with ZSTD compression is 6.8x smaller than CSV, and queries run 22-60x faster depending on how many columns they touch.

## Key takeaways

- On 11,198,026 NYC TLC yellow taxi rows, Parquet/ZSTD is 172,016,004 bytes (164 MB) versus 1,172,769,059 bytes (1.09 GB) for CSV — 6.8x smaller. Parquet/Snappy is 228,421,875 bytes (218 MB) — 5.1x smaller.
- Query speedups versus CSV range from ~22x (all 20 columns) to ~60x (single-column aggregate). `count(*)` is ~160x because it reads Parquet row-group metadata, not data.
- The fair lower-bound headline is 22x, not 160x. DuckDB's parallel CSV reader is already fast (~0.5 s over 1.1 GB). The Parquet win is columnar pruning, metadata, and compression.
- Environment: DuckDB v1.5.5, Apple M3 Max, 36 GB RAM, local SSD, both formats OS-cached. Timings are the median of 3 runs via `.timer on`.
- The dataset is public. Download, export, and query commands that reproduce this benchmark are below.

## What is the dataset?

NYC TLC yellow taxi trips for January–March 2025. Public data from the [TLC trip record page](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page).

- **11,198,026 rows**
- **20 columns**, including `cbd_congestion_fee` (added by NYC TLC in the 2025 Q1 data)

Columns: `VendorID`, `tpep_pickup_datetime`, `tpep_dropoff_datetime`, `passenger_count`, `trip_distance`, `RatecodeID`, `store_and_fwd_flag`, `PULocationID`, `DOLocationID`, `payment_type`, `fare_amount`, `extra`, `mta_tax`, `tip_amount`, `tolls_amount`, `improvement_surcharge`, `total_amount`, `congestion_surcharge`, `Airport_fee`, `cbd_congestion_fee`.

## How the benchmark works

Official TLC Parquet files for 2025-01, 2025-02, and 2025-03 were loaded into one table, then exported once to CSV (with header) and to Parquet with Snappy and ZSTD via `COPY … TO … (FORMAT PARQUET, COMPRESSION …)`.

Five queries were timed against the CSV file and the Parquet/ZSTD file.

- **Engine:** DuckDB v1.5.5
- **Hardware:** Apple M3 Max, 36 GB RAM, local SSD
- **Cache:** both formats OS-cached
- **Timing:** median of 3 runs via `.timer on`

Not measured: gzip CSV, other hardware, other DuckDB versions, cloud object storage.

## File size and query speed

Same 11,198,026 rows in every file.

| Format | Size | vs CSV |
|---|---|---|
| CSV | 1,172,769,059 bytes (1.09 GB) | — |
| Parquet (Snappy) | 228,421,875 bytes (218 MB) | **5.1x smaller** |
| Parquet (ZSTD) | 172,016,004 bytes (164 MB) | **6.8x smaller** |

Query speed, CSV vs Parquet-ZSTD, median of 3:

| Query | CSV | Parquet | Speedup |
|---|---|---|---|
| `SELECT count(*)` | 0.485 s | 0.003 s | **~160x** |
| `SELECT avg(trip_distance)` | 0.490 s | 0.008 s | **~60x** |
| `GROUP BY passenger_count` (count + avg) | 0.506 s | 0.021 s | **~24x** |
| Filtered sum (`WHERE trip_distance > 10`) | 0.496 s | 0.013 s | **~38x** |
| Top-5 by `total_amount` (all 20 columns) | 0.705 s | 0.032 s | **~22x** |

`count(*)` answers from Parquet row-group metadata without scanning data. That is why it is ~160x, and why 22x (all columns) is the number to quote as a lower bound.

## Why is Parquet faster?

Three mechanisms, not “CSV is slow.”

**Columnar pruning.** A Parquet read loads only the columns the query names. `avg(trip_distance)` decodes one column; the all-columns top-5 decodes all 20. That is why speedup falls from ~60x to ~22x as more columns are touched. CSV parses every field of every row either way. This is the same column-skipping behavior described in the [columnar storage guide](/learn/columnar-storage-guide/).

**Row-group metadata.** Parquet stores a row count per row group. `SELECT count(*)` returns from that metadata in 0.003 s versus 0.485 s on CSV — ~160x. That is a real format advantage. It is not a general “Parquet reads 160x faster” claim.

**Compression.** Parquet/ZSTD is 6.8x smaller than the CSV (164 MB vs 1.09 GB), so there is less data to read even when a column is needed.

**What did not help: row-group skipping.** Parquet can skip whole row groups when a filter falls outside a row group's min/max footer stats. On this file it never fires: the data is in pickup-time order, long trips appear in all 92 row groups, and not one has a `trip_distance` max below 10. The filtered sum's ~38× is column pruning and decompression only — the same mechanisms as every other row. Pushdown pays off when the filter column correlates with row-group order, such as a date filter on time-partitioned files.

DuckDB's parallel CSV reader is already fast in absolute terms: about 0.5 s over 1.1 GB on this machine. The Parquet win is architectural — pruning, metadata, and compression — not a slow CSV implementation.

The fair lower-bound headline is **22x** (all 20 columns). Use **22-60x** when the question is “how much faster is Parquet,” and reserve ~160x for an explanation of `count(*)`. For a broader look at choosing the format, see [why choose Parquet](/learn/why-choose-parquet-table-file-format/).

## Reproduce it yourself

Source files (three months):

```bash
curl -O https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2025-01.parquet
curl -O https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2025-02.parquet
curl -O https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2025-03.parquet
```

Load and export to CSV, Snappy, and ZSTD:

```sql
CREATE TABLE taxi AS
    SELECT * FROM read_parquet('yellow_tripdata_2025-*.parquet', union_by_name = true);

SELECT count(*) FROM taxi;
-- 11198026

COPY taxi TO 'taxi.csv' (FORMAT CSV, HEADER);
COPY taxi TO 'taxi_snappy.parquet' (FORMAT PARQUET, COMPRESSION SNAPPY);
COPY taxi TO 'taxi_zstd.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);

-- ls -la taxi.csv taxi_snappy.parquet taxi_zstd.parquet
```

Five timed queries (median of 3 runs each):

```sql
.timer on

-- Query 1: count(*)
SELECT count(*) FROM read_csv_auto('taxi.csv');
SELECT count(*) FROM read_parquet('taxi_zstd.parquet');

-- Query 2: single-column aggregate
SELECT avg(trip_distance) FROM read_csv_auto('taxi.csv');
SELECT avg(trip_distance) FROM read_parquet('taxi_zstd.parquet');

-- Query 3: GROUP BY (count + avg)
SELECT passenger_count, count(*), avg(total_amount)
FROM read_csv_auto('taxi.csv')
GROUP BY passenger_count ORDER BY 1;

SELECT passenger_count, count(*), avg(total_amount)
FROM read_parquet('taxi_zstd.parquet')
GROUP BY passenger_count ORDER BY 1;

-- Query 4: filtered aggregate
SELECT sum(total_amount) FROM read_csv_auto('taxi.csv') WHERE trip_distance > 10;
SELECT sum(total_amount) FROM read_parquet('taxi_zstd.parquet') WHERE trip_distance > 10;

-- Query 5: top-5, all 20 columns
SELECT * FROM read_csv_auto('taxi.csv') ORDER BY total_amount DESC LIMIT 5;
SELECT * FROM read_parquet('taxi_zstd.parquet') ORDER BY total_amount DESC LIMIT 5;
```

File sizes and the 11,198,026 row count should match this page exactly. Timings will vary by machine; the table above is the median of 3 runs on the hardware listed in the method.

## When CSV still wins

CSV is the better file when you need:

- Interchange with tools that do not support columnar formats
- Human-readable diffs
- Streaming or appending rows without rewriting a file

This benchmark does not measure those workflows. It measures size and query time on a local, OS-cached copy of the same 11.2 million rows.