Postgres restore time benchmark (2026)

We restored the same PostgreSQL 17 databases, from a 400 MB Supabase project up to a 10 GB SaaS-style database with 18.8 million rows, on one 2 vCPU / 8 GB VM and timed every step. With durability settings turned off on the target, the 10 GB restore took about 12 minutes, and most of that went to rebuilding indexes and foreign keys, not to loading rows.

Measured October 17, 2026 with pg_restore 17.10 and BackupDrill CLI 2.3.0.

Key numbers

  • 12 minutes

    12 minutes to restore a 10 GB PostgreSQL database with 18.8 million rows on a 2 vCPU / 8 GB VM (722 seconds). The 1 GB database took 69 seconds and the 5 GB database 6.1 minutes.

  • 77%

    77% of the 10 GB restore went to building indexes, constraints and foreign keys (559 seconds). Loading the 18.8 million rows took 163 seconds.

  • 185 seconds

    185 seconds, the longest single step of the 10 GB restore, went to rebuilding one pgvector HNSW index over 200,000 embeddings of 384 dimensions. Loading the largest table, 13.5 million event rows, took 141 seconds.

  • 17–22%

    17–22% of the database size: that's how large the compressed custom-format pg_dump was for all four databases. The 10 GB database produced a 1.74 GiB dump.

  • 27 of 27

    27 of 27 restore drills passed on databases from 0.4 GB to 10 GB, including a 400 MB Supabase Free-plan project, across two BackupDrill versions (CLI 2.2.5 and 2.3.0).

How long a restore takes

Each dump was restored into an empty PostgreSQL 17 database on the same VM: once with a plain pg_restore and twice inside a BackupDrill drill. The bucket sat on the same machine, so download time barely shows up. Over a network, add the time it takes to move the dump.

Restore time by database size, in seconds
DatabaseRowsDumppg_restore, 1 jobDrill restore stepFull drill
396 MiBSupabase Free project723,50074.6 MiB19.4 s20.1 s24.1 s
1.03 GiBsynthetic2,172,000226.5 MiB65.9 s69.1 s75.2 s
5.04 GiBsynthetic9,222,0001.00 GiB379.8 s6.3 min366.0 s6.1 min381.5 s6.4 min
10.05 GiBsynthetic18,822,0001.74 GiB725.9 s12.1 min721.6 s12.0 min795.3 s13.3 min

Sizes are pg_database_size of the source database. The plain pg_restore ran once per size; the two drill columns are the average of two BackupDrill drills. A full drill also downloads the dump, starts the database, checks every table and removes the database afterwards.

  • 12 minutes

    12 minutes to restore a 10 GB PostgreSQL database with 18.8 million rows on a 2 vCPU / 8 GB VM (722 seconds). The 1 GB database took 69 seconds and the 5 GB database 6.1 minutes.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    Restore step of a BackupDrill 2.3.0 drill, average of two runs. Sizes are pg_database_size of the source; the 10 GB database is 10.05 GiB and its dump 1.74 GiB. The target ran with durability settings off, as described under How we tested.

  • Within 5%

    Within 5% of a plain single-job pg_restore of the same dump into identically configured PostgreSQL: BackupDrill's drill restore ranged from 4% faster to 5% slower at every size from 0.4 GB to 10 GB.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    BackupDrill 2.3.0 ran two parallel restore jobs on this 2 vCPU machine and checked every table afterwards. The plain pg_restore ran once per size.

Where the time goes

A custom-format dump holds the rows and the definitions of indexes and constraints, not the indexes themselves. pg_restore has to build every index and check every foreign key again, and on these databases that was most of the work. The table below is one single-job pg_restore of the 10 GB database, timed step by step from its --verbose output.

Time per step of a single-job pg_restore of the 10 GB database
StepTimeShare
Build the pgvector HNSW index (200,000 embeddings, 384 dimensions)184.8 s25%
Build the JSONB GIN index on events145.5 s20%
Load the events table (13.5 million rows)141.3 s19%
Add the three foreign keys on events102.8 s14%
Add the primary key on events57.7 s8%
Build the (project_id, created_at) index on events43.3 s6%
Everything else: five smaller tables, their indexes and constraints, RLS policies, triggers50.5 s7%
Total725.9 s100%
  • 77%

    77% of the 10 GB restore went to building indexes, constraints and foreign keys (559 seconds). Loading the 18.8 million rows took 163 seconds.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    Restore step of a BackupDrill 2.3.0 drill, average of two runs.

  • 185 seconds

    185 seconds, the longest single step of the 10 GB restore, went to rebuilding one pgvector HNSW index over 200,000 embeddings of 384 dimensions. Loading the largest table, 13.5 million event rows, took 141 seconds.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    One single-job pg_restore run, timed from its --verbose output.

  • 1.8x

    1.8x slower: the same 1 GB restore took 110 seconds on stock PostgreSQL settings and 61 seconds with the settings BackupDrill uses for throwaway restore-test databases.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    Single-job pg_restore, one run each, from a test run earlier the same day. The faster settings turn off fsync, synchronous_commit and full_page_writes and raise maintenance_work_mem to 960 MB and shared_buffers to 1.9 GB. Turning off fsync is only safe for a database you can rebuild if the machine crashes mid-restore.

Dump size and backup time

  • 17–22%

    17–22% of the database size: that's how large the compressed custom-format pg_dump was for all four databases. The 10 GB database produced a 1.74 GiB dump.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    Database size is pg_database_size, which counts index pages. The dump only stores index definitions, which is part of why it is so much smaller.

  • 25 seconds

    25 seconds to back up a 400 MB Supabase Free-plan database through the Session pooler from a VM in the same region (Ashburn to us-east-1). The compressed dump was 75 MB.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    Custom-format dump of the public schema: 24.8 seconds with plain pg_dump, 24.9 seconds with BackupDrill.

Did everything come back?

Each drill checks that every table in the backup's manifest exists after the restore and that the tables the manifest estimates to have rows are not empty. For the 400 MB and 1 GB databases we also compared exact row counts and every database object between the source and the restored copy; the exceptions are listed below.

  • 27 of 27

    27 of 27 restore drills passed on databases from 0.4 GB to 10 GB, including a 400 MB Supabase Free-plan project, across two BackupDrill versions (CLI 2.2.5 and 2.3.0).

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

  • 0 differences

    0 differences between the source and the restored database in tables, exact row counts, 16 indexes (B-tree, JSONB GIN, pg_trgm and pgvector HNSW), 7 RLS policies, functions, triggers, enum types and sequence values, checked object by object on the 400 MB Supabase project and the 1 GB database.

    Source: BackupDrill, “Postgres restore time benchmark”, October 17, 2026

    Three things did not come back, by design: the foreign key to auth.users (the drill database has no auth.users table), table-level GRANTs (the dump is taken without privileges) and the supabase_vault extension (the drill database's PostgreSQL image doesn't include it).

How we tested

The machine was one cloud VM with 2 vCPU (Intel Xeon Platinum 8255C), 7.5 GiB of RAM and cloud block storage, running Ubuntu 24.04 and Docker 29. Every restore went into a fresh PostgreSQL 17 container (the pgvector/pgvector:pg17 image) on that VM, using pg_restore 17.10.

All four databases share one schema, modeled on a multi-tenant SaaS app with an AI feature: orgs, users, projects, orders, an events table with JSONB payloads and a documents table with 384-dimension pgvector embeddings, plus 16 indexes, 7 RLS policies, triggers, functions and an enum type. The 400 MB database is a real Supabase Free-plan project (PostgreSQL 17.6, us-east-1). The 1, 5 and 10 GB databases are synthetic and live in a PostgreSQL 17 container on the VM; most of their size is event rows.

BackupDrill CLI 2.3.0 backed up each database with pg_dump in custom format (public schema only, without owners or privileges) to an S3-compatible bucket on the same VM, so the timings measure the restore and not the network. The timings on this page come from restoring each dump twice in a BackupDrill drill and once with a plain single-job pg_restore.

Every restore target used the settings BackupDrill uses for a restore-test database it deletes afterwards: fsync, synchronous_commit and full_page_writes off, wal_level minimal, shared_buffers 1.9 GB and maintenance_work_mem 960 MB. Before each plain pg_restore we created the same extensions and Supabase auth stubs that BackupDrill creates, so both restores did the same work. BackupDrill 2.3.0 ran two parallel restore jobs on this machine.

Repeat runs were within 0–4% of each other in most cases. The same restores ran 3–15% slower that afternoon than they had that morning on the same VM, so compare the numbers on this page with each other rather than with timings from another day or machine.

BackupDrill runs scheduled backups and restore drills for Supabase projects, and the drill numbers here come from our own CLI. We are not affiliated with Supabase or the PostgreSQL project.

What we didn’t test

  • Machines with more cores. Everything ran on 2 vCPU. With more cores, pg_restore --jobs can build several indexes at the same time, so a bigger machine can beat these numbers.
  • Restoring into a new Supabase project. The speed then depends on the project’s compute size and the connection to it.
  • Supabase’s own restore from daily backups or point-in-time recovery. The Free plan has neither, so we couldn’t time it or measure how long the project is offline while it runs.
  • Downloading the dump over the internet. The 10 GB database’s 1.74 GiB dump took 15 seconds to download from a bucket on the same VM. We also ran drills against a remote bucket, but those timings mostly measured the test VM’s network connection, so they aren’t on this page.
  • Other schemas. A database with more indexes per gigabyte, larger vector indexes or thousands of small tables will spend its time differently.
  • Production durability settings at other sizes. We ran stock PostgreSQL settings once, on the 1 GB database: 110 seconds, against 61 seconds with the drill settings. We didn’t measure the effect of fsync on its own.

Cite this page

BackupDrill. "Postgres restore time benchmark (2026)." https://backupdrill.com/research/postgres-restore-time. Updated October 17, 2026.