---
title: "How to convert Parquet to Arrow"
date: "2026-06-15"
author: "Al Brown"
category: "Engineering"
excerpt: "Convert a Parquet file to the Arrow IPC (Feather) format in one command with clickhouse-local — columnar in, columnar out, types preserved, no upload and no server."
---

# How to convert Parquet to Arrow

To convert a Parquet file to Arrow, use [`clickhouse local`](/resources/engineering/what-is-clickhouse-local). It runs SQL directly on files from the command line, with no server to install. It's part of ClickHouse, so the same query scales to billions of rows when you outgrow your laptop.

Install it with [`clickhousectl`](https://clickhouse.com/docs/install):


```bash
curl https://clickhouse.com/cli | sh   # install clickhousectl
clickhousectl local use latest         # download ClickHouse and put it on your PATH
```

Then read the converted Arrow file back to confirm the round-trip:


```bash
clickhouse local -q "SELECT * FROM file('events.arrow') LIMIT 5"
```

```response
2026-01-01	1	GB	click	5	1
2026-01-02	2	US	view	6.01	2
2026-01-03	3	DE	signup	7.02	3
2026-01-04	4	FR	purchase	8.03	4
2026-01-05	5	IN	click	9.04	5
```

The Parquet file is read in place with no import step: `clickhouse local` infers the schema from the Parquet footer and writes Arrow IPC bytes directly to disk, so a file larger than RAM converts without loading it all into memory at once.

## Types carry across unchanged

Parquet already stores a typed schema, so there is nothing to infer from raw text and nothing to guess. The types ClickHouse reads from the Parquet footer are the types it writes into the Arrow file. Compare the two with `DESCRIBE`:


```bash
clickhouse local -q "DESCRIBE file('events.parquet')"
clickhouse local -q "DESCRIBE file('events.arrow')"
```

```text
event_date	Date32
event_id	UInt64
country	String
action	String
amount	Float64
sessions	UInt8
```

Identical on both sides. Note the columns are not `Nullable`: Parquet recorded them as required, and Arrow keeps that. (A column that *was* nullable in the Parquet schema stays nullable in Arrow.) This is the difference from converting out of CSV, where everything starts as text and types are inferred. Parquet to Arrow is lossless for the common numeric, string, date, and timestamp types because both formats descend from the same columnar type system.

Read the Arrow file straight back to confirm the values round-tripped:


```bash
clickhouse local -q "SELECT * FROM file('events.arrow') LIMIT 5"
```

```response
2026-01-01	1	GB	click	5	1
2026-01-02	2	US	view	6.01	2
2026-01-03	3	DE	signup	7.02	3
2026-01-04	4	FR	purchase	8.03	4
2026-01-05	5	IN	click	9.04	5
```

## Options worth knowing

This is the information that an upload-a-file converter site can't give you, because you control the writer:

- **`.feather` is the same format.** Feather (v2) *is* the Arrow IPC file format. If a tool wants a `.feather` file, write the exact same bytes and name them `.feather`:

  ```bash
  clickhouse local -q "SELECT * FROM file('events.parquet') INTO OUTFILE 'events.feather' FORMAT Arrow"
  ```

- **Arrow IPC file vs stream.** `FORMAT Arrow` writes the random-access *file* format (with a footer). For a pipe or socket, `FORMAT ArrowStream` writes the streaming variant instead. Reading the stream back needs the format named explicitly: `file('events.arrows', 'ArrowStream')`.

- **Arrow output is uncompressed by default.** That is deliberate. Arrow is built for fast in-memory handoff, not for storage. On the 3,000,000-row sample, the Arrow file is *larger* than its Parquet source (68 MB vs 23 MB) because Parquet compresses on disk and uncompressed Arrow doesn't. If you want a smaller Arrow file, turn on block compression:

  ```bash
  clickhouse local -q "
  SELECT * FROM file('events_large.parquet')
  INTO OUTFILE 'events_large.arrow'
  FORMAT Arrow
  SETTINGS output_format_arrow_compression_method='zstd'"
  ```

  That takes the file from 68 MB to 39 MB and still reads as a normal Arrow file. `lz4_frame` is the other supported codec when you want decode speed over ratio.

If you do want long-term, well-compressed storage rather than an in-memory exchange buffer, keep the data as Parquet — see [what is a Parquet file](/resources/engineering/what-is-parquet-file).

## Why convert at all

Parquet is the right format on disk: compressed, columnar, great for repeated scans. Arrow is the right format in memory: it's the zero-copy layout that pandas, pyarrow, and Polars read without re-parsing. Converting Parquet to Arrow is how you hand a dataset to an in-process analytics tool with no deserialization tax. You'd reach for the Arrow file when the consumer is a Python or R process that maps Arrow directly, and keep Parquet when the consumer reads from disk or object storage.

## Verify nothing was dropped

A conversion is only done when the row count and values match. Check both files agree:


```bash
clickhouse local -q "
SELECT
  (SELECT count() FROM file('events_large.parquet')) AS parquet_rows,
  (SELECT count() FROM file('events_large.arrow'))   AS arrow_rows"
```

```response
3000000	3000000
```

## How fast is it?

On the 3,000,000-row `events_large.parquet` (~23 MB Parquet in, ~68 MB Arrow out), the full conversion runs in:


```bash
clickhouse local -q "SELECT * FROM file('events_large.parquet') INTO OUTFILE 'events_large.arrow' TRUNCATE FORMAT Arrow"
```

**~0.30 seconds**, best of three with a warm OS page cache, on an Apple M4 Pro laptop (14 cores, 24 GB RAM). Because both sides are columnar, the work is mostly reading column chunks and re-laying them out as Arrow record batches; there is no row-by-row text parsing.

## Reverse direction?

Going the other way is the same one line with the formats swapped. See [how to convert Arrow to Parquet](/resources/engineering/convert-arrow-to-parquet).

## In Python with chDB

If you're already in a Python process, [chDB](/resources/engineering/what-is-chdb) runs the identical SQL in-process, with no separate binary. Use the same `SELECT ... FORMAT Arrow` and write the bytes to a file:


```python
import chdb

arrow_bytes = chdb.query("SELECT * FROM file('events.parquet') FORMAT Arrow").bytes()
with open("events.arrow", "wb") as f:
    f.write(arrow_bytes)

# Or skip the file entirely and get a DataFrame, since Arrow is the handoff:
df = chdb.query("SELECT * FROM file('events.parquet')", "DataFrame")
print(df.shape)
```

```text
(20, 6)
```

For reading Arrow back into a DataFrame, see [how to read an Arrow file in Python with chDB](/resources/engineering/read-arrow-file-python).

## Run it yourself

The complete, runnable example lives in the ClickHouse examples repo: `generate.sh` to create the sample Parquet files (including the 3M-row file used for the timing above), `run.sh` with every command on this page, `run.py` / `run.ipynb` for the chDB path, and `expected_output.txt`.

[github.com/ClickHouse/examples/tree/main/local-analytics/convert-parquet-to-arrow](https://github.com/ClickHouse/examples/tree/main/local-analytics/convert-parquet-to-arrow)

The same SQL that converts a local file scales unchanged to a ClickHouse server or [ClickHouse Cloud](https://clickhouse.com/cloud) when the data outgrows your laptop.
