# Migrating 50,000 Airtable records to Postgres in 15 minutes

A base with 50,000 records used to be an all-day migration if those records carried attachments. It is not anymore. We rebuilt how AT Migrator runs a migration: the row data now lands in PostgreSQL in about 15 minutes, and attachment files copy to your storage afterward in a separate pass. Here is what changed and why.

## Key takeaways

- Row data and attachments are now two separate phases. Your database is queryable before the files finish copying.
- A real 50,000-record Airtable base migrated its rows in about 15 minutes; attachments copied in the background after.
- The old pipeline copied every attachment inline, one row at a time. That is why attachment-heavy bases used to take hours.
- Migrations resume where they left off, and a handful of failed file copies no longer sink the whole run.

| Metric | Result |
|--------|-------:|
| Records migrated | 50,000 |
| To row data in Postgres | ~15 min |
| Attachment phase | Separate |
| Rows lost | 0 |

## How it used to work

Every migration ran as a single pass. For each record, AT Migrator would download that record's attachments from Airtable's CDN, upload them to your bucket, and only then write the row to Postgres. Rows waited on files.

That is fine for a 500-row base. On a base with tens of thousands of rows and an attachment on most of them, it fell over. Files were copied roughly two at a time for the entire run, and Airtable's signed file URLs expire after about two hours, so a long migration would start racing its own links.

We had an internal test base of around 25,000 records with a file on nearly every row. It was on track to take the better part of a day, and the row data was stuck behind the files the whole time.

## What we changed

We split the migration into two phases.

**First, the row data.** All of it goes in before any file is touched: larger insert batches, more records transformed in parallel, and the schema, linked records, and formulas rebuilt as it runs. For the 50k base this phase finished in about 15 minutes.

**Then, attachments.** A dedicated phase walks the rows that are already in Postgres, re-reads Airtable for fresh file links so nothing has expired, and copies files into your storage many at a time, with retries. It updates each row's file references in place as it goes. Large files stream instead of buffering, so memory stays flat.

The practical result: your Postgres database is usable within minutes of starting, and the files fill in behind it. You can watch that second phase run, with a rough time remaining.

> **The rate limit is not the ceiling anymore.** Airtable caps every base at five API requests per second, and reading records is bound by that. But the old pipeline spent most of a large migration on attachments, not reads. With files moved off the critical path, the row phase now scales with record count roughly linearly instead of stalling on file I/O.

## The 50,000-record test

We ran a real base end to end: 50,000 records with attachments, migrating to PostgreSQL.

- **Row data:** about 15 to 16 minutes. Every table live and queryable in Postgres at that point.
- **Attachments:** copied afterward, in the background, into the customer's own storage, with progress and an estimate shown the whole time.
- **Data loss:** none. If a file fails to copy after retries it keeps a working reference and gets flagged, so you can retry just those files without re-running the migration.

## Try it on your base

If you have a large Airtable base, especially one with a lot of attachments, that you have been putting off moving, now is a good time. Start with a migration plan at https://www.atmigrator.com/pricing or book a discovery call at https://www.atmigrator.com/contact. There is also a live estimator at https://www.atmigrator.com/#estimate that will size the migration window for your record and attachment counts.
